package com.androidtest3d.view;

/**
 * Android Game Tutorial Project.
 *
 * Copyright (C) 2011  Philipp Jenke
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import android.util.Log;

import com.androidtest3d.math.DimensionsNotMatchException;
import com.androidtest3d.math.MyMatrix;
import com.androidtest3d.math.MyVector;

/**
 * Represents a camera.
 */
public class Camera {

	/**
	 * Identifier of the class in the logger.
	 */
	private static final String TAG = "Camera";

	/**
	 * Default eye position.
	 */
	private static final MyVector CAMERA_DEFAULT_EYE = MyVector.makeVector3(0,
			0, -5);

	/**
	 * Default reference point.
	 */
	private static final MyVector CAMERA_DEFAULT_REF = MyVector.makeVector3(0,
			0, 0);

	/**
	 * Default up vector.
	 */
	private static final MyVector CAMERA_DEFAULT_UP = MyVector.makeVector3(0,
			1, 0);

	/**
	 * Position of the eye.
	 */
	private MyVector eye;

	/**
	 * Reference point of the camera (look at)
	 */
	private MyVector ref;

	/**
	 * Up-vector of the camera.
	 */
	private MyVector up;

	/**
	 * Default opening angle.
	 */
	private static final float DEFAULT_OPENING_ANGLE = 45.0f;

	/**
	 * Default near plane.
	 */
	private static final float DEFAULT_NEAR_PLANE = 0.01f;

	/**
	 * Default far plane.
	 */
	private static final float DEFAULT_FAR_PLANE = 100.0f;

	/**
	 * Opening angle of the camera.
	 */
	private float openingAngle = DEFAULT_OPENING_ANGLE;

	/**
	 * Near plane.
	 */
	private float nearPlane = DEFAULT_NEAR_PLANE;

	/**
	 * Far plane.
	 */
	private float farPlane = DEFAULT_FAR_PLANE;

	/**
	 * Constructor.
	 */
	public Camera() {
		try {
			eye.copyFrom(CAMERA_DEFAULT_EYE);
			ref.copyFrom(CAMERA_DEFAULT_REF);
			up.copyFrom(CAMERA_DEFAULT_UP);
		} catch (DimensionsNotMatchException e) {
			Log.e(TAG, e.getMessage());
		}

	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public MyVector getEye() {
		return eye;
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public MyVector getRef() {
		return ref;
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public MyVector getUp() {
		return up;
	}

	/**
	 * Setter.
	 * 
	 * @param e
	 */
	public void setEye(MyVector e) {
		try {
			eye.copyFrom(e);
		} catch (Exception e1) {
			Log.e(TAG, e1.getMessage());
		}
	}

	/**
	 * Setter.
	 * 
	 * @param e
	 */
	public void setRef(MyVector e) {
		try {
			ref.copyFrom(e);
		} catch (Exception e1) {
			Log.e(TAG, e1.getMessage());
		}
	}

	/**
	 * Setter.
	 * 
	 * @param e
	 */
	public void setUp(MyVector e) {
		try {
			up.copyFrom(e);
		} catch (Exception e1) {
			Log.e(TAG, e1.getMessage());
		}
	}

	/**
	 * Rotate the camera around the up vector and the eye position.
	 */
	public void rotateVertically(final double angle) {

		try {
			// Normalize direction
			MyVector oldDirection = eye.subtract(ref);
			double length = oldDirection.getNorm();

			oldDirection.copyFrom(oldDirection.divide(length));

			// Apply rotation
			MyMatrix rotationMatrix = MyMatrix.getRotationMatrix(up, angle);
			MyVector newDirection = rotationMatrix.multiply(oldDirection);
			newDirection.normalize();
			MyVector newEye = ref.add(newDirection.multiply(length));

			// Assign new coordinate frame
			eye = newEye;
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
		}
	}

	/**
	 * Rotate the camera around a horizontal axis.
	 */
	public void rotateHorizontally(final double angle) {
		try {
			// Normalize direction
			MyVector oldDirection = eye.subtract(ref);
			double length = oldDirection.getNorm();

			oldDirection.normalize();

			// Compute rotation axis
			MyVector axis = oldDirection.cross(up);
			axis.normalize();

			// Apply rotation
			MyMatrix rotationMatrix = MyMatrix.getRotationMatrix(axis, angle);
			MyVector newDirection = rotationMatrix.multiply(oldDirection);
			newDirection.normalize();
			MyVector newEye = ref.add(newDirection.multiply(length));

			// Assign new coordinate frame
			eye = newEye;
			up.copyFrom(axis.cross(newDirection));
			up.normalize();
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
		}
	}

	/**
	 * Getter.
	 * 
	 * @return
	 */
	public float getOpeningAngle() {
		return openingAngle;
	}

	/**
	 * @return the nEAR_PLANE
	 */
	public float getNearPlane() {
		return nearPlane;
	}

	/**
	 * @return the fAR_PLANE
	 */
	public float getFarPlane() {
		return farPlane;
	}
}
